//  Raytracer Assignment week 4 
//	by Zse Sun and Gerard Knap 
//
//  Framework for a raytracer
//  File: scene.cpp
//
//  Created for the Computer Science course "Introduction Computer Graphics"
//  taught at the University of Groningen by Tobias Isenberg.
//
//  Authors:
//    Maarten Everts
//    Jasper van de Gronde
//	  Zhe Sun
//
//  This framework is inspired by and uses code of the raytracer framework of 
//  Bert Freudenberg that can be found at
//  http://isgwww.cs.uni-magdeburg.de/graphik/lehre/cg2/projekt/rtprojekt.html 
//

#include "scene.h"
#include "material.h"
#include "sphere.h"
#include "stdio.h"

extern bool shadowMode;
// reflection time
extern int maxRecursionDepth;
// super sample factor for anti-aliasing
extern int superSampleFactor;
float recursionCoef = 0.6;
//Image* texture = 0;

Color static recursiveTrace(std::vector<Object*> objects, std::vector<Light*> lights, Triple eye, const Ray &ray, int level, bool isTopCall, float coef);
Color static recursiveGoochTrace(std::vector<Object*> objects, std::vector<Light*> lights, Triple eye, const Ray &ray, int level, bool isTopCall, float coef);
Color static getMaterialColor(Object* obj, Point hit);

Color PhongScene::trace(const Ray &ray) {
// 	if(texture == 0) {
// 		printf("Reading texture...\n");
// 		texture =  new Image("earthmap1k.png");
// 		printf("Texture size: w:%d, h:%d \n", texture->width(), texture->height());
// 	}
    return recursiveTrace(objects, lights, eye, ray, maxRecursionDepth, true, 1.0);
}

/**
	This function will recursively trace a ray until level < 0. 
 */
	  int tempIndex = 0;
    int tempIndex1 = 0;
Color  recursiveTrace(std::vector<Object*> objects, std::vector<Light*> lights, Triple eye, const Ray &ray, int level, bool isTopCall, float coef) {
    if (level < 0) {
        return Color(0.0, 0.0, 0.0);
    }
    float eps = 1.0;
	double u,v;
    // Find hit object and distance
    Triple newEye;
    Ray *newRay;
	//Color color;
    Hit min_hit(std::numeric_limits<double>::infinity(), Vector());
    Object *obj = NULL;


    for (unsigned int i = 0; i < objects.size(); ++i) {
        Hit hit(objects[i]->intersect(ray));
        if (hit.t < min_hit.t) {
            min_hit = hit;
            obj = objects[i];
        }
    }
    
    // No hit? Return background color.
    if (!obj) return Color(0.0, 0.0, 0.0);

    Material *material = obj->material; //the hit objects material
    Point hit = ray.at(min_hit.t); //the hit point
    Vector N = min_hit.N; //the normal at hit point
    newEye = hit + eps * N;
    Vector V = -ray.D; //the view vector
    newRay = new Ray(newEye, ray.D - 2 * ray.D.dot(N) * N);

    // formular from the book Page 83    
    // implementation according to the tutorial sheet lab 1
    
	//Color color = material->color;
	Color color = getMaterialColor(obj, hit);
	

    // Ambient
    Color totalColor = isTopCall || true ? coef * material->ka * color : Color(0, 0, 0); //after some trial-and-error it has been decided to always add the ambient component to get the best visual results

    // Iterate all the lights
    vector<Light*>::iterator iter;
    for (iter = lights.begin(); iter != lights.end(); iter++) {

        Point lPos = (*iter)->position;
        Color lColor = (*iter)->color;
        bool ignoreLight = false;
        if (shadowMode) {
            // Ray from intersection point to the light
            Vector lightN = (lPos - hit).normalized();
            Ray rayIntersectToLight(hit + (eps * lightN), lightN);

            // judge if there is any object on the ray from intersection point to the light
            ignoreLight = false;
            for (unsigned int i = 0; i < objects.size(); i++) {
                Hit min_hit(std::numeric_limits<double>::infinity(), Vector());
                Hit lightHit = objects[i]->intersect(rayIntersectToLight);
				//objects[i]->

                if (lightHit.t < min_hit.t) {
                    ignoreLight = true;
                    break;
                }
            }
        }
        if (ignoreLight) {
            continue;
        }

        // get vector from the hit point to the light
        Vector L = (lPos - hit).normalized();

        // get the reflect vector
        Vector R = 2 * (L.dot(N)) * N - L;

        // max(0, (n.l))
        double dot_N_L = (N.dot(L) > 0) ? N.dot(L) : 0;
        // max(0, (R.V))
        double dot_R_V = (R.dot(V) > 0) ? R.dot(V) : 0;

        // Diffuse component
        if (true || isTopCall) { //after some trial-and-error it has been decided to always add the diffuse component to get the best visual results
            totalColor += coef * lColor * color * material->kd * dot_N_L;
        }

        // specular component
        // Attention: Specular does not depend on material color!!!
        totalColor += coef * lColor * material->ks * pow(dot_R_V, material->n);
    }
    if (material-> ks == 0) {
        return totalColor;
    } else {
        return totalColor + recursiveTrace(objects, lights, newEye, *newRay, level - 1, false, recursionCoef * coef);
    }

}

//this function only works on spehere's because of the typecast
Color  getMaterialColor(Object* obj, Point hit) {
	double u,v;
	int x,y, w, h;
	Sphere* sphere;

	if(!(obj->material->textureFile.empty())) { 
		//sphere = (Sphere*) obj;
		//sphere->mapCoord(hit, &u, &v);
		obj->mapCoord(hit, &u, &v);
		x = u * obj->material->texture->width();
		y = v * obj->material->texture->height();
		return obj->material->texture->get_pixel(x,y);
	} else {
		return obj->material->color;
		
	}
}



Color GoochScene::trace(const Ray &ray) {
	

   // Find hit object and distance
    Hit min_hit(std::numeric_limits<double>::infinity(),Vector());
    Object *obj = NULL;
    for (unsigned int i = 0; i < objects.size(); ++i) {
        Hit hit(objects[i]->intersect(ray));
        if (hit.t<min_hit.t) {
            min_hit = hit;
            obj = objects[i];
        }
    }

    // No hit? Return background color.
    if (!obj) return Color(0.0, 0.0, 0.0);

    Material *material = obj->material;            //the hit objects material
    Point hit = ray.at(min_hit.t);                 //the hit point
    Vector N = min_hit.N;                          //the normal at hit point
    Vector V = -ray.D;                             //the view vector

    // formular from the book Page 83    
    // implementation according to the tutorial sheet lab 1
    Color color = material->color;          
    
    Color totalColor = Color(0.0,0.0,0.0);
    
    Point lPos = lights[0]->position;
    Color lColor = lights[0]->color;
    
    // get vector from the hit point to the light
    Vector L = (lPos - hit).normalized();
    
    // get the reflect vector 
    Vector R = 2*(L.dot(N))*N - L;
    
    // max(0, (n.l))
    double dot_N_L = (N.dot(L) > 0) ? N.dot(L) : 0;
    // max(0, (R.V))
    double dot_R_V = (R.dot(V) > 0) ? R.dot(V) : 0;
    
//    Color kBlue = Color(0.0, 0.0, 0.55);
  //  Color kYellow = Color(0.3, 0.3, 0.0);
    //double alpha = 0.25;
    //double beta = 0.5;

        // Here, must be sphere
    //if (obj->isEdge(ray))
    if (fabs(N.dot(V)) < 0.3)
      return Color(0.0, 0.0, 0.0);
    
    
    Color kCool = kBlue + alpha * lColor * color * material->kd;
    Color kWarm = kYellow + beta * lColor * color * material->kd; 

    // Diffuse component
    if (dot_N_L >= 0.0) {
	    totalColor += kCool * (1-dot_N_L)/2 + kWarm * (1+dot_N_L)/2;
    }

    // specular component
    // Attention: Specular does not depend on material color!!!
    totalColor += lColor * material->ks * pow(dot_R_V, material->n);
  
    return totalColor;
}



Color NormalBufferScene::trace(const Ray &ray) {
    // Find hit object and distance
    Hit min_hit(std::numeric_limits<double>::infinity(), Vector());
    Object *obj = NULL;
    for (unsigned int i = 0; i < objects.size(); ++i) {
        Hit hit(objects[i]->intersect(ray));
        if (hit.t < min_hit.t) {
            min_hit = hit;
            obj = objects[i];
        }
    }

    // No hit? Return background color.
    if (!obj) return Color(0.0, 0.0, 0.0);

    //the normal at hit point
    Vector N = min_hit.N;

    // map [-1..1] to [0..1]
    return Color((N.x + 1.0) / 2.0, (N.y + 1.0) / 2.0, (N.z + 1.0) / 2.0);
}

#define MAX_D 1000

Color ZBufferScene::trace(const Ray &ray) {
    // Find hit object and distance
    Hit min_hit(std::numeric_limits<double>::infinity(), Vector());
    Object *obj = NULL;
    for (unsigned int i = 0; i < objects.size(); ++i) {
        Hit hit(objects[i]->intersect(ray));
        if (hit.t < min_hit.t) {
            min_hit = hit;
            obj = objects[i];
        }
    }

    // No hit? Return background color.
    if (!obj) return Color(0.0, 0.0, 0.0);

    double col = (MAX_D - min_hit.t) / MAX_D;

    return Color(col, col, col);
}

void Scene::render(Image &img) {
    int w = img.width();
    int h = img.height();

    // load the texture
    for (unsigned int i = 0; i < objects.size(); ++i) {
	 Object *obj = objects[i];
	if(obj->material->textureFile.length() > 0) {
		printf("Reading texture %s...\n",  obj->material->textureFile.c_str());
		obj->material->texture =  new Image(obj->material->textureFile.c_str());
		printf("Texture size: w:%d, h:%d \n", obj->material->texture->width(), obj->material->texture->height());
	}
    }    
    
    // lens vector: from eye to the centers
    Vector lensV = (viewCenterPoint - eye).normalized();
    // right vector
    Vector rightV = (lensV.cross(upVector)).normalized();
    // make the lenght of right vector same as up vector
    rightV = rightV * upVector.length();
    
    for (int y = 0; y < h; y++) {
        for (int x = 0; x < w; x++) {
	  
	  // implement the anti-aliasing
	  Color col = Color(0.0,0.0,0.0);
	  float step = 1.0 / superSampleFactor;
	  float initPixel = step / 2;
	  int i,j;
	  float dx,dy;
	  
	  for (i=0, dx=initPixel; i < superSampleFactor; dx=dx+step, i++){
	    for (j=0, dy=initPixel; j < superSampleFactor; dy=dy+step, j++){
  #if 0
	      // Look from front
	      Point pixel(x + dx, h - 1 - y + dy, 0);
  #elif 0
	      // Look from right
	      Point pixel(0, x + dx, h - 1 - y + dy);
  #else
	      // use camera parameter
	      float viewX = x + dx;
	      float viewY = h - 1 - y + dy;
	      
	      float deltaViewX = viewX - w / 2.0;
	      float deltaViewY = viewY - h / 2.0;
	      
	      Point pixel(viewCenterPoint + rightV*deltaViewX + upVector*deltaViewY);
  #endif
  	      Ray ray(eye, (pixel - eye).normalized());
	      col += trace(ray);
	    }
	  }
	  
	  col = col / (superSampleFactor*superSampleFactor);
          col.clamp();
          img(x, y) = col;
        }
    }
}

void Scene::addObject(Object *o) {
    objects.push_back(o);
}

void Scene::addLight(Light *l) {
    lights.push_back(l);
}

void Scene::setEye(Triple e) {
    eye = e;
}

void Scene::setViewCenterPoint(Triple c) {
    viewCenterPoint = c;
}

void Scene::setViewArea(int w, int h) {
  viewW = w;
  viewH = h;
}

void Scene::setViewUpVector(Vector u){
  upVector = u;
}
